﻿using System;
using System.Collections.Generic;
using System.Text;

using Heaven.Geometry;
using Microsoft.Xna.Framework.Graphics;

namespace Heaven.Xna.Graphics
{
    /// <summary>
    /// Represents a visual geometry
    /// </summary>
    class VisualGeometry
    {
        #region Fields

        // Graphics device
        GraphicsDevice device; 

        // Vertex buffer
        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        VertexDeclaration declaration;
        // Size of vertex in bytes
        int vertexSize;
        // A numbe of vertices
        int vertexCount;
        // A number of triangles
        int triangleCount;

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="device">Graphics device</param>
        /// <param name="polygonal">Polygonal object</param>
        public VisualGeometry(GraphicsDevice device, Polygonal polygonal)
        {
            this.device = device;
            VertexElement[] elements = CreateElements(polygonal);
            this.declaration = new VertexDeclaration(device, elements);
            this.vertexSize = GetVertexSize(elements);

            Initialize(polygonal);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Renders the polygonal object
        /// </summary>
        public void Draw()
        {
            device.VertexDeclaration = declaration;
            device.Vertices[0].SetSource(vertexBuffer, 0, vertexSize);
            device.Indices = indexBuffer;
            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertexCount, 0, triangleCount);
        }

        #endregion

        #region Private Methods

        void Initialize(Polygonal polygonal)
        {
            // Gets vertex elements of the given model
            VertexElement[] elements = CreateElements(polygonal);
            UnifiedVertex[] vertices = polygonal.ExtractVertices();
            // Makes buffer with required size
            int vertexSize = GetVertexSize(elements);
            Single[] buffer = new Single[(vertexSize / 4) * vertices.Length];

            // Sends vertices to the buffer
            for (int i = 0, j = 0; i < vertices.Length; i++, j += vertexSize / 4)
            {
                int counter = 0;

                // The sequence must be the same 
                // as in GetElements method (!)
                if (polygonal.HasPositions)
                {
                    buffer[j + counter] = (float)vertices[i].Position.X;
                    buffer[j + counter + 1] = (float)vertices[i].Position.Y;
                    buffer[j + counter + 2] = (float)vertices[i].Position.Z;
                    counter += 3;
                }
                if (polygonal.HasNormals)
                {
                    buffer[j + counter] = (float)vertices[i].Normal.X;
                    buffer[j + counter + 1] = (float)vertices[i].Normal.Y;
                    buffer[j + counter + 2] = (float)vertices[i].Normal.Z;
                    counter += 3;
                }
                if (polygonal.HasTangents)
                {
                    buffer[j + counter] = (float)vertices[i].Tangent.X;
                    buffer[j + counter + 1] = (float)vertices[i].Tangent.Y;
                    buffer[j + counter + 2] = (float)vertices[i].Tangent.Z;
                    counter += 3;
                }
                if (polygonal.HasBinormals)
                {
                    buffer[j + counter] = (float)vertices[i].Binormal.X;
                    buffer[j + counter + 1] = (float)vertices[i].Binormal.Y;
                    buffer[j + counter + 2] = (float)vertices[i].Binormal.Z;
                    counter += 3;
                }
                if (polygonal.IsTextured)
                {
                    buffer[j + counter] = (float)vertices[i].Texture.X;
                    buffer[j + counter + 1] = (float)vertices[i].Texture.Y;
                    counter += 2;
                }
                if (polygonal.HasColor)
                {
                    buffer[j + counter] = (float)vertices[i].Color.X;
                    buffer[j + counter + 1] = (float)vertices[i].Color.Y;
                    buffer[j + counter + 2] = (float)vertices[i].Color.Z;
                    buffer[j + counter + 3] = (float)vertices[i].Color.W;
                    counter += 4;
                }
            }

            // Uploads vertices
            vertexCount = vertices.Length;
            vertexBuffer = new VertexBuffer(device, buffer.Length * 4, BufferUsage.WriteOnly);
            vertexBuffer.SetData(buffer);

            // Uploads indecies
            Int32[] indices = polygonal.ExtractIndecies();
            triangleCount = indices.Length / 3;
            if (indices.Length > UInt16.MaxValue)
            {
                // If a number of indices > 65536
                indexBuffer = new IndexBuffer(device, indices.Length * 4, BufferUsage.WriteOnly, IndexElementSize.ThirtyTwoBits);
                indexBuffer.SetData(indices);
            }
            else
            {
                // If a number of indices < 65536
                UInt16[] indices16 = new UInt16[indices.Length];
                for (int i = 0; i < indices.Length; i++) indices16[i] = (UInt16)indices[i];
                indexBuffer = new IndexBuffer(device, indices.Length * 2, BufferUsage.WriteOnly, IndexElementSize.SixteenBits);
                indexBuffer.SetData(indices16);
            }
        }

        // Get a number of bytes which requaries to keep data
        int GetVertexSize(VertexElement[] elements)
        {
            int lastElementSize = 0;
            VertexElementFormat type = elements[elements.Length - 1].VertexElementFormat;
            switch (type)
            {
                case VertexElementFormat.Vector2: lastElementSize = 4 * 2; break;
                case VertexElementFormat.Vector3: lastElementSize = 4 * 3; break;
                default: throw new ArgumentException("The given vertex elements include unsupported declaration type " + type.ToString());
            }

            return elements[elements.Length - 1].Offset + lastElementSize;
        }

        /// <summary>
        /// Формат вершины, необходимый для
        /// ее размещения в видеопамяти
        /// </summary>
        VertexElement[] CreateElements(Polygonal polygonal)
        {
            List<VertexElement> elements = new List<VertexElement>();

            // Смещение в формате, изначально 0, 
            // но указывая каждый раз инкрементируем счетчик
            short offset = 0;

            // The sequence must be the same 
            // as in Initialize() method (!)
            if (polygonal.HasPositions)
            {
                elements.Add(new VertexElement(0, offset,
                    VertexElementFormat.Vector3,
                    VertexElementMethod.Default,
                    VertexElementUsage.Position, 0));
                offset += 4 * 3;
            }
            if (polygonal.HasNormals)
            {
                elements.Add(new VertexElement(0, offset,
                    VertexElementFormat.Vector3,
                    VertexElementMethod.Default,
                    VertexElementUsage.Normal, 0));
                offset += 4 * 3;
            }
            if (polygonal.HasTangents)
            {
                elements.Add(new VertexElement(0, offset,
                    VertexElementFormat.Vector3,
                    VertexElementMethod.Default,
                    VertexElementUsage.Tangent, 0));
                offset += 4 * 3;
            }
            if (polygonal.HasBinormals)
            {
                elements.Add(new VertexElement(0, offset,
                    VertexElementFormat.Vector3,
                    VertexElementMethod.Default,
                    VertexElementUsage.Binormal, 0));
                offset += 4 * 3;
            }
            if (polygonal.IsTextured)
            {
                elements.Add(new VertexElement(0, offset,
                    VertexElementFormat.Vector2,
                    VertexElementMethod.Default,
                    VertexElementUsage.TextureCoordinate, 0));
                offset += 4 * 2;
            }
            if (polygonal.HasColor)
            {
                elements.Add(new VertexElement(0, offset,
                    VertexElementFormat.Vector4,
                    VertexElementMethod.Default,
                    VertexElementUsage.Color, 0));
                offset += 4 * 4;
            }

            return elements.ToArray();
        }

        #endregion

        #region Destructor

        ~VisualGeometry()
        {
            if (vertexBuffer != null) vertexBuffer.Dispose();
            if (indexBuffer != null) indexBuffer.Dispose();
            if (declaration != null) declaration.Dispose();
        }

        /// <summary>
        /// Disposes unmanaged resources
        /// </summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            vertexBuffer.Dispose();
            indexBuffer.Dispose();
            declaration.Dispose();
        }

        #endregion
    }
}
